#pragma once

#include <zkutil/ZooKeeperHolder.h>
#include <common/logger_useful.h>
#include <DB/Common/Exception.h>

namespace zkutil
{
	class Lock
	{
	public:
		/// lock_prefix - относительный путь до блокировки в ZK. Начинается со слеша
		/// lock_name - имя ноды блокировки в ZK
		Lock(
			zkutil::ZooKeeperHolderPtr zookeeper_holder_,
			const std::string & lock_prefix_,
			const std::string & lock_name_,
			const std::string & lock_message_ = "",
			bool create_parent_path_ = false)
		:
			zookeeper_holder(zookeeper_holder_),
			lock_path(lock_prefix_ + "/" + lock_name_),
			lock_message(lock_message_),
			log(&Logger::get("zkutil::Lock"))
		{
			auto zookeeper = zookeeper_holder->getZooKeeper();
			if (create_parent_path_)
				zookeeper->createAncestors(lock_prefix_);

			zookeeper->createIfNotExists(lock_prefix_, "");
		}

		Lock(const Lock &) = delete;
		Lock(Lock && lock) = default;
		Lock & operator=(const Lock &) = delete;

		~Lock()
		{
			try
			{
				unlock();
			}
			catch (const zkutil::KeeperException & e)
			{
				DB::tryLogCurrentException(__PRETTY_FUNCTION__);
			}
		}

		enum Status
		{
			UNLOCKED,
			LOCKED_BY_ME,
			LOCKED_BY_OTHER,
			END
		};
		std::string status2String(Status status);

		/// проверяет создана ли эфемерная нода и кто ее владелец.
		Status tryCheck() const;

		void unlock();
		void unlockOrMoveIfFailed(std::vector<zkutil::Lock> & failed_to_unlock_locks);

		bool tryLock();

		/// путь к ноде блокировки в zookeeper
		const std::string & getPath() { return lock_path; }

	private:
		zkutil::ZooKeeperHolderPtr zookeeper_holder;
		/// пока храним указатель на хендлер, никто не может переиницализировать сессию с zookeeper
		using ZooKeeperHandler = zkutil::ZooKeeperHolder::UnstorableZookeeperHandler;
		std::unique_ptr<ZooKeeperHandler> locked;

		std::string lock_path;
		std::string lock_message;
		Logger * log;

	};
}
